package org.apache.maven.repository.metadata;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

import org.apache.maven.artifact.ArtifactScopeEnum;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;

/**
 * Default conflict resolver.Implements closer newer first policy by default, but could be
 * configured via plexus
 *
 * @author <a href="mailto:oleg@codehaus.org">Oleg Gusakov</a>
 */
@Component(role = GraphConflictResolver.class)
public class DefaultGraphConflictResolver implements GraphConflictResolver {
  /**
   * artifact, closer to the entry point, is selected
   */
  @Requirement(role = GraphConflictResolutionPolicy.class)
  protected GraphConflictResolutionPolicy policy;

  // -------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------
  public MetadataGraph resolveConflicts(MetadataGraph graph, ArtifactScopeEnum scope)
      throws GraphConflictResolutionException {
    if (policy == null) {
      throw new GraphConflictResolutionException("no GraphConflictResolutionPolicy injected");
    }

    if (graph == null) {
      return null;
    }

    final MetadataGraphVertex entry = graph.getEntry();
    if (entry == null) {
      return null;
    }

    if (graph.isEmpty()) {
      throw new GraphConflictResolutionException("graph with an entry, but not vertices do not exist");
    }

    if (graph.isEmptyEdges()) {
      return null; // no edges - nothing to worry about
    }

    final TreeSet<MetadataGraphVertex> vertices = graph.getVertices();

    try {
      // edge case - single vertex graph
      if (vertices.size() == 1) {
        return new MetadataGraph(entry);
      }

      final ArtifactScopeEnum requestedScope = ArtifactScopeEnum.checkScope(scope);

      MetadataGraph res = new MetadataGraph(vertices.size());
      res.setVersionedVertices(false);
      res.setScopedVertices(false);

      MetadataGraphVertex resEntry = res.addVertex(entry.getMd());
      res.setEntry(resEntry);

      res.setScope(requestedScope);

      for (MetadataGraphVertex v : vertices) {
        final List<MetadataGraphEdge> ins = graph.getIncidentEdges(v);
        final MetadataGraphEdge edge = cleanEdges(v, ins, requestedScope);

        if (edge == null) { // no edges - don't need this vertex any more
          if (entry.equals(v)) { // unless it's an entry point.
                                 // currently processing the entry point - it should not have any
                                 // entry incident edges
            res.getEntry().getMd().setWhy("This is a graph entry point. No links.");
          } else {
            // System.out.println("--->"+v.getMd().toDomainString()
            // +" has been terminated on this entry set\n-------------------\n"
            // +ins
            // +"\n-------------------\n"
            // );
          }
        } else {
          // System.out.println("+++>"+v.getMd().toDomainString()+" still has "+edge.toString() );
          // fill in domain md with actual version data
          ArtifactMetadata md = v.getMd();
          ArtifactMetadata newMd = new ArtifactMetadata(md.getGroupId(), md.getArtifactId(), edge.getVersion(),
              md.getType(), md.getScopeAsEnum(), md.getClassifier(), edge.getArtifactUri(),
              edge.getSource() == null ? "" : edge.getSource().getMd().toString(), edge.isResolved(),
              edge.getTarget() == null ? null : edge.getTarget().getMd().getError());
          MetadataGraphVertex newV = res.addVertex(newMd);
          MetadataGraphVertex sourceV = res.addVertex(edge.getSource().getMd());

          res.addEdge(sourceV, newV, edge);
        }
      }
      MetadataGraph linkedRes = findLinkedSubgraph(res);
      // System.err.println("Original graph("+graph.getVertices().size()+"):\n"+graph.toString());
      // System.err.println("Cleaned("+requestedScope+")
      // graph("+res.getVertices().size()+"):\n"+res.toString());
      // System.err.println("Linked("+requestedScope+")
      // subgraph("+linkedRes.getVertices().size()+"):\n"+linkedRes.toString());
      return linkedRes;
    } catch (MetadataResolutionException e) {
      throw new GraphConflictResolutionException(e);
    }
  }

  // -------------------------------------------------------------------------------------
  private MetadataGraph findLinkedSubgraph(MetadataGraph g) {
    if (g.getVertices().size() == 1) {
      return g;
    }

    List<MetadataGraphVertex> visited = new ArrayList<MetadataGraphVertex>(g.getVertices().size());
    visit(g.getEntry(), visited, g);

    List<MetadataGraphVertex> dropList = new ArrayList<MetadataGraphVertex>(g.getVertices().size());

    // collect drop list
    for (MetadataGraphVertex v : g.getVertices()) {
      if (!visited.contains(v)) {
        dropList.add(v);
      }
    }

    if (dropList.size() < 1) {
      return g;
    }

    // now - drop vertices
    TreeSet<MetadataGraphVertex> vertices = g.getVertices();
    for (MetadataGraphVertex v : dropList) {
      vertices.remove(v);
    }

    return g;
  }

  // -------------------------------------------------------------------------------------
  private void visit(MetadataGraphVertex from, List<MetadataGraphVertex> visited, MetadataGraph graph) {
    if (visited.contains(from)) {
      return;
    }

    visited.add(from);

    List<MetadataGraphEdge> exitList = graph.getExcidentEdges(from);
    // String s = "|---> "+from.getMd().toString()+" - "+(exitList == null ? -1 : exitList.size()) +
    // " exit links";
    if (exitList != null && exitList.size() > 0) {
      for (MetadataGraphEdge e : graph.getExcidentEdges(from)) {
        visit(e.getTarget(), visited, graph);
      }
    }
  }

  // -------------------------------------------------------------------------------------
  private MetadataGraphEdge cleanEdges(MetadataGraphVertex v, List<MetadataGraphEdge> edges, ArtifactScopeEnum scope) {
    if (edges == null || edges.isEmpty()) {
      return null;
    }

    if (edges.size() == 1) {
      MetadataGraphEdge e = edges.get(0);
      if (scope.encloses(e.getScope())) {
        return e;
      }

      return null;
    }

    MetadataGraphEdge res = null;

    for (MetadataGraphEdge e : edges) {
      if (!scope.encloses(e.getScope())) {
        continue;
      }

      if (res == null) {
        res = e;
      } else {
        res = policy.apply(e, res);
      }
    }

    return res;
  }
  // -------------------------------------------------------------------------------------
  // -------------------------------------------------------------------------------------
}
